/****************************************************************************
 * Copyright (c) 2010 Torkild U. Resheim
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Torkild U. Resheim  - initial API and implementation
 *****************************************************************************/
package no.resheim.buildmonitor.ui.views;

import java.net.MalformedURLException;
import java.net.URI;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import no.resheim.buildmonitor.core.BuildmonitorCore;
import no.resheim.buildmonitor.core.data.Job;
import no.resheim.buildmonitor.core.data.Server;
import no.resheim.buildmonitor.ui.properties.JobPropertySource;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.views.properties.IPropertyDescriptor;

/**
 * 
 * @author Torkild Ulvøy Resheim
 * @since 1.0
 */
public class JobsView extends ViewPart {

	private static final String TAG_ID = "id";

	private static final String TAG_POSITION = "position";

	private static final String TAG_COLUMN = "COLUMN";

	private static final String TAG_COLUMNS = "COLUMNS";

	/**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "no.resheim.buildmonitor.ui.views.JobsView";

	private static IWebBrowser fEditorBrowser;

	private TreeViewer viewer;
	private Action doubleClickAction;

	private static final String VIEW_CONTEXT_ID = "no.resheim.buildmonitor.context";
	private IContextActivation contextActivation;
	private final JobPropertySource propertySource;
	/**
	 * A map of columns that should be shown in the view. The identifiers are
	 * the same as the ones used in the {@link JobPropertySource}. The integer
	 * corresponds to the index of the column (which may not be the same as the
	 * display order).
	 */
	private final SortedMap<Integer, String> columnsToShow;

	private JobViewLabelProvider labelProvider;

	private static class NameSorter extends ViewerSorter {
	}

	/**
	 * The constructor.
	 */
	public JobsView() {
		propertySource = new JobPropertySource(null);
		columnsToShow = new TreeMap<Integer, String>();
	}

	@Override
	public void dispose() {
		super.dispose();
		IContextService contextService = (IContextService) getSite()
				.getService(IContextService.class);
		contextService.deactivateContext(contextActivation);
	}

	private void addColumn(String id) {
		Assert.isNotNull(id);
		IPropertyDescriptor[] descriptors = propertySource
				.getPropertyDescriptors();
		IPropertyDescriptor descriptor = null;
		for (IPropertyDescriptor iPropertyDescriptor : descriptors) {
			if (iPropertyDescriptor.getId().equals(id)) {
				descriptor = iPropertyDescriptor;
			}
		}
		columnsToShow.put(viewer.getTree().getColumnCount(), id.toString()
				.trim());
		TreeViewerColumn tc = new TreeViewerColumn(viewer, SWT.NONE);
		tc.getColumn().setWidth(130);
		if (viewer.getTree().getColumnCount() == 1) {
			tc.getColumn().setMoveable(false);
		} else {
			tc.getColumn().setMoveable(true);
		}
		tc.getColumn().setResizable(true);
		tc.getColumn().setText(descriptor.getDisplayName());
		tc.setLabelProvider(labelProvider);
		tc.getColumn().setData(id);
		viewer.refresh();
	}

	/**
	 * Remove the column tied to the specified property id
	 * 
	 * @param id
	 *            the property identifier
	 */
	private void removeColumn(Object id) {
		int[] columns = viewer.getTree().getColumnOrder();
		columnsToShow.clear();
		for (int i : columns) {
			TreeColumn tc = viewer.getTree().getColumn(i);
			String columnId = (String) tc.getData();
			if (columnId == id) {
				tc.dispose();
			} else {
				columnsToShow.put(columns[i], columnId);
			}
		}
		viewer.refresh();
	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it.
	 */
	@Override
	public void createPartControl(Composite parent) {
		// We need a context so that we for instance can handle the "Delete"
		// command.
		IContextService contextService = (IContextService) getSite()
				.getService(IContextService.class);
		contextActivation = contextService.activateContext(VIEW_CONTEXT_ID);
		Composite c = new Composite(parent, SWT.NONE);
		c.setLayout(new GridLayout(1, true));
		ImprovedFilteredTree tree = new ImprovedFilteredTree(c, SWT.BORDER
				| SWT.SINGLE | SWT.FULL_SELECTION, new PatternFilter(), true);
		viewer = tree.getViewer();
		JobViewContentProvider provider = new JobViewContentProvider();
		viewer.setContentProvider(provider);
		FancyToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE);
		labelProvider = new JobViewLabelProvider(this);

		viewer.getTree().setHeaderVisible(true);
		viewer.setSorter(new NameSorter());
		viewer.setInput(BuildmonitorCore.getDefault().getServers());
		// Make sure we register the tree viewer as a selection provider or we
		// will not not be able to get contributed commands using selections to
		// work.
		getSite().setSelectionProvider(viewer);
		// We have some columns to add. These are the defaults or we have
		// restored settings from a previous session.
		if (columnsToShow.size() > 0) {
			for (Entry<Integer, String> entry : columnsToShow.entrySet()) {
				addColumn(entry.getValue());
			}
		}
		// Create our actions
		makeActions();
		hookContextMenu();
		hookDoubleClickAction();
		contributeToActionBars();
	}

	@Override
	public void init(IViewSite site, IMemento memento) throws PartInitException {
		super.init(site, memento);
		if (memento != null) {
			IMemento columns = memento.getChild(TAG_COLUMNS);
			if (columns != null) {
				IMemento[] items = columns.getChildren(TAG_COLUMN);
				for (IMemento iMemento : items) {
					String id = iMemento.getString(TAG_ID);
					// It should not already be there, but if it is we're not
					// going to add another one.
					if (!columnsToShow.containsValue(id)) {
						columnsToShow
								.put(iMemento.getInteger(TAG_POSITION), id);
					}
				}
				if (columnsToShow.size() > 0) {
					return;
				}
			}
		}
		// Add the default columns
		columnsToShow.put(0, JobPropertySource.DISPLAY_NAME);
		columnsToShow.put(1, JobPropertySource.LAST_BUILD);
		columnsToShow.put(2, JobPropertySource.ETA);
	}

	@Override
	public void saveState(IMemento memento) {
		super.saveState(memento);
		// The column number used in columnsToShow as the key will correspond
		// with the order of the columns as they are added in the tree. However
		// the actual display order may differ. We will store this display order
		// so that we can reconstruct it when the view state is restored.
		int[] order = viewer.getTree().getColumnOrder();
		IMemento columns = memento.createChild(TAG_COLUMNS);
		for (int i : order) {
			IMemento column = columns.createChild(TAG_COLUMN);
			String id = (String) viewer.getTree().getColumn(i).getData();
			column.putString(TAG_ID, id);
			column.putInteger(TAG_POSITION, order[i]);
		}
	}

	private static class FancyToolTipSupport extends ColumnViewerToolTipSupport {

		protected FancyToolTipSupport(ColumnViewer viewer, int style,
				boolean manualActivation) {
			super(viewer, style, manualActivation);
		}

		@Override
		protected Composite createToolTipContentArea(Event event,
				Composite parent) {
			Composite comp = new Composite(parent, SWT.NONE);
			GridLayout l = new GridLayout(1, false);
			l.horizontalSpacing = 0;
			l.marginWidth = 0;
			l.marginHeight = 0;
			l.verticalSpacing = 0;

			comp.setLayout(l);
			// TODO: Links followed in the browser must open in a new view!
			Browser browser = new Browser(comp, SWT.BORDER);
			browser.setText(getText(event));
			browser.setLayoutData(new GridData(400, 200));

			return comp;
		}

		@Override
		public boolean isHideOnMouseDown() {
			return false;
		}

		public static final void enableFor(ColumnViewer viewer, int style) {
			new FancyToolTipSupport(viewer, style, false);
		}
	}

	private void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				JobsView.this.fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
		// We're going to make it really easy on ourselves and use the property
		// support mechanism to provide the information we need. This already
		// contains a list of all viewable properties along with identifiers
		// and display names.
		MenuManager menu2 = new MenuManager("Show", "1");
		final IPropertyDescriptor[] properties = propertySource
				.getPropertyDescriptors();
		for (final IPropertyDescriptor iPropertyDescriptor : properties) {
			Action action = new Action(iPropertyDescriptor.getDisplayName(),
					Action.AS_CHECK_BOX) {
				@Override
				public void run() {
					if (this.isChecked()) {
						addColumn(iPropertyDescriptor.getId().toString());
					} else {
						removeColumn(iPropertyDescriptor.getId());
					}
				}

			};
			action.setChecked(columnsToShow.containsValue(iPropertyDescriptor
					.getId()));
			// We always want the display name
			if (iPropertyDescriptor.getId().equals(
					JobPropertySource.DISPLAY_NAME)) {
				action.setEnabled(false);
			}
			menu2.add(action);
		}
		manager.add(menu2);
	}

	private void fillContextMenu(IMenuManager manager) {
		// Other plug-ins can contribute there actions here
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

	private void fillLocalToolBar(IToolBarManager manager) {
	}

	public static IWebBrowser getBrowser() {
		int flags = IWorkbenchBrowserSupport.AS_EDITOR;
		if (fEditorBrowser == null) {
			try {
				fEditorBrowser = PlatformUI.getWorkbench().getBrowserSupport()
						.createBrowser(flags, ID, "", "");
			} catch (PartInitException e) {
				e.printStackTrace();
			}
		}
		return fEditorBrowser;
	}

	private void makeActions() {
		doubleClickAction = new Action() {
			@Override
			public void run() {
				ISelection selection = viewer.getSelection();
				Object obj = ((IStructuredSelection) selection)
						.getFirstElement();
				URI uri = null;
				if (obj instanceof Server) {
					uri = ((Server) obj).getUri();
				}
				if (obj instanceof Job) {
					uri = ((Job) obj).getUri();
				}
				if (uri != null) {
					try {
						getBrowser().openURL(uri.toURL());
					} catch (PartInitException e) {
						e.printStackTrace();
					} catch (MalformedURLException e) {
						e.printStackTrace();
					}

				}
			}
		};
	}

	private void hookDoubleClickAction() {
		viewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				doubleClickAction.run();
			}
		});
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		viewer.getControl().setFocus();
	}

	public TreeViewer getViewer() {
		return viewer;
	}

	public SortedMap<Integer, String> getColumnsToShow() {
		return columnsToShow;
	}

}